/*
 * ModuleParser.java
 *
 * $Id: ModuleParser.java,v 1.5 2009-09-29 11:34:44 mario Exp $
 */
package org.ceteca.explica.core.installer;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import org.ceteca.explica.client.ClientContext;
import org.ceteca.explica.core.InterfaceExplica;
import org.ceteca.explica.core.util.LoggerFactory;
import org.ceteca.explica.core.util.ResultMessage;

/**
* This class implements a SAX Parser to parse an XML module configuration file.
* <br/>
* Responsibilities:
* <ul>
*  <li>Parse XML module configuration files.</li>
*  <li>Validate XML module configuration files.</li>
* </ul>
* <br/>
* @author Mario García García <mario@imagos.es>
 * Copyright (c) 2008 Fundación Centro Tecnolóxico da Carne
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <br/>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * <br/>
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
public class ModuleParser extends DefaultHandler {
	/** Temporary store to keep the content of XML elements of type module */
	private ModuleOT module;
	/** Temporary store to keep the content of XML elements of type entity */
	private EntityOT entity;
	/** Temporary store to keep the content of XML elements of type field */
	private FieldOT field;
	/** Temporary store to keep the content of XML elements of type PCDATA */
	private String content;
	
	/**
     * Parser constructor. Creates a new instance of the parser and parses
     * the file specified. 
     * @param aPathFile String, path of the module configuration file that will 
     * be parsed.
     * @throws ParserConfigurationException if a parser cannot be created which 
     * satisfies the requested configuration. 
     * @throws SAXException If there is a problem processing the configuration file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     * @throws IOException in case the file doesn't exist or cannot be opened/read.
     */
    public ModuleParser(String aPathFile)
    		throws ParserConfigurationException, IOException, SAXException {
    	
        try {
            // SAX Parsing of the XML glade file
            SAXParserFactory tmpFactory = SAXParserFactory.newInstance();
            tmpFactory.setValidating(true);

            // Create the SAX parser instance.
            SAXParser parser = tmpFactory.newSAXParser();
            // Get the XMLReader instance embedded into the SAX Parser. This
            // XMLReader instance will allow the configuration of parsing parameters.
            XMLReader xmlReader = parser.getXMLReader();
            
            xmlReader.setContentHandler(this);
            xmlReader.setErrorHandler(this);
            
            LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				info(InterfaceModuleParser.MODULE_LOG_CREATE_PARSER);
            
            // Open the file to check if the file exists
            File f = new File(aPathFile);
            if (f == null || !f.exists())
            	throw new IOException("The file: " + aPathFile + " doesn't exist or cannot be opened!");
            
            LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				info(String.format(InterfaceModuleParser.MODULE_LOG_PARSE_FILEPATH, f.getPath()));
            
            InputStream fis = new FileInputStream(f);
            LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
            	info(String.format(InterfaceModuleParser.MODULE_LOG_PARSE_FILEPATH, fis));
            
            InputSource source = new InputSource(fis);
            LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
        		info(String.format(InterfaceModuleParser.MODULE_LOG_PARSE_FILEPATH, source));
            
            xmlReader.parse(source);
        } catch (ParserConfigurationException e) {
        	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				error(InterfaceModuleParser.MODULE_LOG_ERROR_GETTING_PARSER, e);
            throw e;
        } catch (IOException e) {
        	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
    			error(InterfaceModuleParser.MODULE_LOG_ERROR_READING_FILE, e);
            throw e;
        } catch (SAXException e) {
        	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				error(InterfaceModuleParser.MODULE_LOG_ERROR_PARSING_FILE, e);
            throw e;
        }
    }
    
    /**
     * Parser constructor. Creates a new instance of the parser and parses
     * the file specified. 
     * @param is InputStream, input stream of the module configuration file
     * that will be parsed.
     * @throws ParserConfigurationException if a parser cannot be created which 
     * satisfies the requested configuration. 
     * @throws SAXException If there is a problem processing the configuration file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     * @throws IOException in case the file doesn't exist or cannot be opened/read.
     */
    public ModuleParser(InputStream is)
    		throws ParserConfigurationException, IOException, SAXException {
    	
    	System.out.println("Constructor de ModuleParser");
    	
        try {
            // SAX Parsing of the XML glade file
            SAXParserFactory tmpFactory = SAXParserFactory.newInstance();
            System.out.println("11111111111111111");
            tmpFactory.setValidating(true);
            System.out.println("22222222222222222");

            // Create the SAX parser instance.
            SAXParser parser = tmpFactory.newSAXParser();
            // Get the XMLReader instance embedded into the SAX Parser. This
            // XMLReader instance will allow the configuration of parsing parameters.
            XMLReader xmlReader = parser.getXMLReader();
            
            System.out.println("3333333333333333333");
            xmlReader.setContentHandler(this);
            System.out.println("4444444444444444444");
            xmlReader.setErrorHandler(this);
            
            System.out.println("5555555555555555555");
            // Set the EntityResolver instance to redirect URIs to secure
            // local versions (especially DTD).
            xmlReader.setEntityResolver(new ModuleEntityResolver());
            
            // Open the file to check if the file exists
            if (is == null)
            	throw new IOException("The input stream is null!"); 
            
            System.out.println("66666666666666666666");
            
            InputSource source = new InputSource(is);
            
            System.out.println("77777777777777777777");
            
            xmlReader.parse(source);
            System.out.println("88888888888888888888");
        } catch (ParserConfigurationException e) {
        	System.out.println("99999999999999999999");
        	e.printStackTrace();
        	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        } catch (IOException e) {
        	System.out.println("00000000000000000000");
        	e.printStackTrace();
        	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        } catch (SAXException e) {
        	System.out.println("AQUIIIIIIIIIIIIIIIII");
        	e.printStackTrace();
        	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				error(e.getMessage());
            throw e;
        }
    }
	
	/**
	 * Gets the PCDATA XML element content.
	 * @return String, parsed content.
	 */
	public String getContent() {
		return content;
	}
	/**
	 * Sets up the PCDATA XML element content.
	 * @param content String, parsed content.
	 */
	public void setContent(String content) {
		this.content = content;
	}
	/**
	 * Updates the content of a PCDATA XML element with the parsed characters.
	 * @param ch char[], set of characters that is being parsed.
	 * @param start int, starting index of the first character parsed.
	 * @param length int, number of characters parsed.
	 */
	public void appendContent(char[] ch, int start, int length) {
		String str = new String(ch, start, length);
		content += str;
	}
	
	/**
	 * Receive notification of the start of the module configuration file 
	 * to parse.
     * @throws SAXException, if there is a problem processing the configuration
     * file (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
	 */
	
	public void startDocument() throws SAXException {
		LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
			info(InterfaceModuleParser.MODULE_LOG_PARSING_START_DOCUMENT);
		
		this.module = null;
	    this.entity = null;
	    this.field = null;
	    
	    // Reset the content of the tag
		this.setContent("");
	}
	
    /**
     * Receive notification of the start of an element.
     * @param uri String, 
     * @param localName String, the local name (without prefix), or the empty string 
     * if Namespace processing is not being performed. 
     * @param qName String, the qualified name (with prefix), or the empty string 
     * if qualified names are not available. 
     * @param attributes Attributes, the specified or defaulted attributes. 
     * @throws SAXException, if there is a problem processing the glade file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     */
    public void startElement(String uri, String localName, String qName, Attributes attributes) 
    			throws SAXException {

    	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
			info(String.format(InterfaceModuleParser.MODULE_LOG_PARSING_START_ELEMENT, qName, uri, localName, this.convertAttsToString(attributes)));
    	
    	// If the tag name is null or empty
    	if (qName == null || qName.length() <= 0) {
    		ResultMessage res = ClientContext.getInstance().getMsgCatalog().
    			newResultMessage(InterfaceModuleParser.ERROR_EMPTY_TAG);
    		throw new SAXException(res.getMessage());
    	}
    	// If starting <module> tag
    	else if (qName.equals(InterfaceModuleParser.MODULE_TAG)) {
	    	// Create a new instance of ModuleOT
	        this.module = new ModuleOT(
	        		attributes.getValue(InterfaceModuleParser.MODULE_NAME_PROPERTYNAME),
	        		attributes.getValue(InterfaceModuleParser.MODULE_MAINCLASS_PROPERTYNAME),
	        		attributes.getValue(InterfaceModuleParser.MODULE_MAINMETHOD_PROPERTYNAME),
	        		attributes.getValue(InterfaceModuleParser.MODULE_ICON_PROPERTYNAME),
	        		attributes.getValue(InterfaceModuleParser.MODULE_LOGCATEGORY_PROPERTYNAME),
	        		attributes.getValue(InterfaceModuleParser.MODULE_LOGCONFIGFILE_PROPERTYNAME),
	        		attributes.getValue(InterfaceModuleParser.MODULE_MSGPROPFILE_PROPERTYNAME)
	        );
	    }
    	// If starting a <entity> tag
    	else if (qName.equals(InterfaceModuleParser.ENTITY_TAG)) {
        	// Create a new instance of EntityOT
    		this.entity = new EntityOT(
    				attributes.getValue(InterfaceModuleParser.ENTITY_KEY_PROPERTYNAME),
    				attributes.getValue(InterfaceModuleParser.ENTITY_NAME_PROPERTYNAME),
    				attributes.getValue(InterfaceModuleParser.ENTITY_ICON_PROPERTYNAME),
    				attributes.getValue(InterfaceModuleParser.ENTITY_IDFIELD_PROPERTYNAME),
    				attributes.getValue(InterfaceModuleParser.ENTITY_HELPERCLASS_PROPERTYNAME),
    				attributes.getValue(InterfaceModuleParser.ENTITY_VISIBLE_PROPERTYNAME)
    		);
    	}
    	// If starting a <field> tag
        else if (qName.equals(InterfaceModuleParser.FIELD_TAG)) {
        	// Create a new instance of FieldOT
    		this.field = new FieldOT(
    				attributes.getValue(InterfaceModuleParser.FIELD_ID_PROPERTYNAME),
    				attributes.getValue(InterfaceModuleParser.FIELD_NAME_PROPERTYNAME),
    				attributes.getValue(InterfaceModuleParser.FIELD_SEARCHABLE_PROPERTYNAME),
					attributes.getValue(InterfaceModuleParser.FIELD_VISIBLE_PROPERTYNAME),
					attributes.getValue(InterfaceModuleParser.FIELD_DETAILENTITY_PROPERTYNAME),
					attributes.getValue(InterfaceModuleParser.FIELD_DETAILFIELD_PROPERTYNAME)
    		);
        }
    	// else if starting any other tag --> Error 
    	else {
    		ArrayList<String> lParams = new ArrayList<String>();
    		lParams.add(qName);
    		lParams.add(InterfaceModuleParser.MODULE_TAG);
    		ResultMessage res = ClientContext.getInstance().getMsgCatalog().
    			newResultMessage(InterfaceModuleParser.ERROR_UNEXPECTED_TAG, lParams);
    		throw new SAXException(res.getMessage());
    	}
    	
    	// Reset the content of the tag
    	this.setContent("");
    }

    /**
     * Receive notification of the end of an element.
     * @param uri String, 
     * @param localName String, the local name (without prefix), or the empty string 
     * if Namespace processing is not being performed. 
     * @param qName String, the qualified name (with prefix), or the empty string 
     * if qualified names are not available. 
     * @throws SAXException, if there is a problem processing the glade file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     */
    public void endElement(String uri, String localName, 
                           String qName) throws SAXException {

    	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
    		info(String.format(InterfaceModuleParser.MODULE_LOG_PARSING_END_ELEMENT, qName, uri, localName));

    	
    	// If the tag name is null or empty
    	if (qName == null || qName.length() <= 0) {
    		ResultMessage res = ClientContext.getInstance().getMsgCatalog().
    			newResultMessage(InterfaceModuleParser.ERROR_EMPTY_TAG);
    		throw new SAXException(res.getMessage());
    	}
    	// If ending <module> tag
    	else if (qName.equals(InterfaceModuleParser.MODULE_TAG)) {
	    }
    	// If ending <entity> tag
    	else if (qName.equals(InterfaceModuleParser.ENTITY_TAG)) {
    		this.module.addEntity(this.entity);
    	}
    	// If ending a <field> tag
        else if (qName.equals(InterfaceModuleParser.FIELD_TAG)) {
        	this.entity.addField(this.field);
        }
    	// else if ending any other tag --> Error 
    	else {
    		ArrayList<String> lParams = new ArrayList<String>();
    		lParams.add(qName);
    		lParams.add(InterfaceModuleParser.MODULE_TAG);
    		ResultMessage res = ClientContext.getInstance().getMsgCatalog().
    			newResultMessage(InterfaceModuleParser.ERROR_UNEXPECTED_TAG, lParams);
    		throw new SAXException(res.getMessage());
    	}
    }

    /**
     * Receive notification of character data inside an element.
     * @param ch char[], The characters. 
     * @param start int, The start position in the character array.
     * @param length int, The number of characters to use from the character array. 
     * @throws SAXException, if there is a problem processing the glade file 
     * (such as incorrect file format, format not compliant with the schema, 
     * unknown tag appeared, etc.).
     */
	public void characters(char[] ch, int start, int length) throws SAXException {
		this.appendContent(ch, start, length);
	}
	
	/**
	 * Receive notification of a parser warning.
	 * @param e SAXParseException, The warning information encoded as an exception. 
	 */
	public void warning(SAXParseException e) throws SAXException {
		e.printStackTrace();
		ResultMessage res = ClientContext.getInstance().getMsgCatalog().
			newResultMessage(InterfaceModuleParser.ERROR_GLADE_PARSER, this.getErrorParams(e));
    	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
    		error(String.format(InterfaceModuleParser.MODULE_LOG_ERROR_PARSING_FILE, res.getMessage()));

    }
	
	/**
	 * Receive notification of a recoverable parser error.
	 * @param e SAXParseException, The warning information encoded as an exception.
	 */
    public void error(SAXParseException e) throws SAXException {
    	e.printStackTrace();
    	// Ignore the identifier not found SAXException
    	if (e.getMessage().contains("An element with the identifier") &&
    		e.getMessage().contains("must appear in the document.")) {
    		// Ignore the exception
    	} // if exception == Identifier not found
    	else {
	    	ResultMessage res = ClientContext.getInstance().getMsgCatalog().
	    		newResultMessage(InterfaceModuleParser.ERROR_GLADE_PARSER, this.getErrorParams(e));
	    	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				error(String.format(InterfaceModuleParser.MODULE_LOG_ERROR_PARSING_FILE, res.getMessage()));
	    	LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
				error(String.format(InterfaceModuleParser.MODULE_LOG_ERROR_PARSING_FILE, e.getMessage()));
			throw new SAXException(res.getMessage());
    	} // else
    }
    
    /**
     * Report a fatal XML parsing error.
     * @param e SAXParseException, The warning information encoded as an exception.
     */
    public void fatalError(SAXParseException e) throws SAXException {
    	e.printStackTrace();
		ResultMessage res = ClientContext.getInstance().getMsgCatalog().
			newResultMessage(InterfaceModuleParser.ERROR_GLADE_PARSER, this.getErrorParams(e));
		LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY).
			error(String.format(InterfaceModuleParser.MODULE_LOG_ERROR_PARSING_FILE, res.getMessage()));
		throw new SAXException(res.getMessage());
    }
    
    /**
     * Gets the parameters of an Exception for printing the formatted
     * error message.
     * @param e SAXParseException, The warning information encoded as an exception.
     */
    private ArrayList<String> getErrorParams(SAXParseException e) {
    	ArrayList<String> lParams = new ArrayList<String>();
    	lParams.add("" + e.getLineNumber());
    	lParams.add("" + e.getColumnNumber());
    	lParams.add(e.getPublicId());
    	lParams.add(e.getSystemId());
    	lParams.add(e.getMessage());
     	return lParams;
    }
    
    /**
     * Format the attributes of the element that is parsing into a String
     * for printing it out in the log.
     * @param atts Attributes, list of attributes of an XML element.
     * @return String, attributes list formatted as a String.
     */
    private String convertAttsToString(Attributes atts) {
    	StringBuffer buf = new StringBuffer();
    	for (int i=0; atts != null && i<atts.getLength(); i++) {
    		buf.append("\n\t");
    		buf.append(atts.getQName(i));
    		buf.append(" --> ");
    		buf.append(atts.getValue(i));
    	}
    	
    	return buf.toString();
    }
    
    /**
     * Gets the final result of the module configuration XML parsed file.
     * @return ModuleOT, all the configuration info loaded for the module.
     */
    public ModuleOT getModule() {
    	return this.module;
    }
    
    /**
	 * Returns tag Id assigned to CVS source file.
	 */
	public static String getRevision() {
		return "$Id: ModuleParser.java,v 1.5 2009-09-29 11:34:44 mario Exp $";
	}
}